GO
1. 什么是Shell变量
1.1. 什么是变量
简单地说,变量就是用一个固定的字符串(也可能是字符、数字等的组合)代替更多、更复杂的内容,该内容里可能还会包含变量、路径、字符串等其它的内容。
变量是暂时存储数据的地方及数据标记,所存储的数据存在于内存空间中,通过正确地调用内存空间中变量的名字就可以取出与变量对应的数据。使用变量的最大好处就是使程序开发更为方便,当然,在编程中使用变量也是必须的,否则就很难完成相关的程序开发工作。
下面是定义变量和打印变量的示例:
变量的赋值方式为:变量名=值
,中间没有任何空格,通过echo命令加上$变量名
即可输出变量的值,变量的内容一般要加双引号,以防止出错,特别是当值里的内容之间有空格时。
1.2. Shell变量的特性
默认情况下,在bash Shell中是不会区分变量类型的,例如:常见的变量类型为整数、字符串、小数等。这和其它强类型语言(例如: Java/C语言)是有区别的,当然,如果需要指定Shell变量的类型,也可以使用declare
显示定义变量的类型,但在一般情况下没有这个需求,Shell开发者在开发脚本时需要自行注意Shell脚本中变量的类型,这对新手来说是个重点也是个难点。
1.3. 变量类型
变量可分为两类:环境变量(全局变量)和普通变量(局部变量)。
环境变量也可称为全局变量,可以在创建它们的Shell及其派生出来的任意子进程Shell中使用,环境变量又可分为自定义环境变量和bash内置的环境变量。
普通变量也可称为局部变量,只能在创建它们的Shell函数或Shell脚本中使用。普通变量一般由开发脚本程序时创建。
2. 环境变量
环境变量一般是指使用export
内置命令导出的变量,用于定义Shell的运行环境,保证Shell命令的正确执行。Shell通过环境变量来确定登录用户名、命令路径、终端类型、登陆目录等,所有的环境变量都是系统全局变量,可用于所有子进程中,这包括编辑器,Shell脚本和各类应用。
环境变量可以在命令行中设置和创建,但用户退出命令行时这些变量值就会丢失,因此,如果希望永久保存环境变狼,可以在用户家目录下的.bash_profile
或.bashrc
(非用户登陆模式特有,例如远程SSH)文件中,或者全局配置/etc/bashrc
(非用户登陆模式特有,例如远程SSH)或/etc/profile
文件中定义。在将环境变量放入上述的文件中后,每次用户登陆时这些变量都将被初始化。
按照系统规范,所有环境变量的名字均采用大写形式。在将环境变量应用于用户进程程序之前,都应该用export
命令导出定义,例如:正确的环境变量定义方法为:export OLDGIRL=1
有一些环境变量,比如HOME、PATH、SHELL、UID、USER
等,在用户登陆之前就已经被/bin/login
程序设置好了。通常环境变量被定义并保存在用户家目录下的.bash_profile
文件或全局的配置文件/etc/profile
中。
在查看设置的变量时,有3个命令可以显示变量的值:set
、env
、declare
(替代早期的typeset)。
set
命令输出所有的变量,包括全局变量和局部变量;env
命令只显示全局变量;declare
命令输出所有的变量、函数、整数和已经导出的变量。set -o
命令显示bash Shell的所有参数配置信息。
例子:set、env和declare输出:
2.1. 自定义环境变量
2.1.1. 设置环境变量
利用export
命令和declare
命令设置环境变量的三种方法:
export 变量名=值
变量名=值 ; export 变量名
declare -x 变量名=值
下面介绍让环境变量永久生效的常用设置文件。
- 用户的环境变量配置:
~/.bashrc
(推荐在此文件中优先设置)和~/.bash_profile
- 全局环境变量的配置:
/etc/profile
、/etc/bashrc
(推荐在此文件中优先设置)和/etc/profile.d/
- 若要在登陆后初始化或显示加载内容,则把脚本文件放在
/etc/profile.d/
下即可(无需加执行权限)。
2.1.2. 设置登陆提示的两种方式
第一种是在/etc/motd
里增加提示的字符串,如下:
登录后显示内容如下:
第二种是在/etc/profile.d/
下面增加如下脚本:
以下是在生产场景下(在Java环境中),自定义环境变量的实例:
提示:上述的环境变量设置通常放在/etc/profile
全局环境变量里。如果是写Java的脚本,那么最好是把上述Java环境配置放入脚本内重新定义,特别是作为定时任务执行的脚本。
2.2. 显示与取消环境变量
2.2.1. 通过echo或printf命令打印环境变量
下面来查看一下常见的系统环境变量:
- $HOME:用户登陆时进入的目录
- $UID:当前用户的UID(用户标识),相当于id-u
- $PWD:当前工作目录的绝对路径名
- $SHELL:当前SHELL
- $USER:当前用户
- ……
通过echo和printf命令打印环境变量:
提示:在写Shell脚本时可以直接使用系统默认的环境变量,一般情况下是不需要重新定义的,在使用定时任务等执行Shell脚本时建议在脚本中重新定义。
2.2.2. 用env或set显示默认的环境变量
用env(printenv)
显示默认环境变量的示例如下:
用set也可以显示环境变量(包括局部变量),如下:
2.2.3. 用unset消除本地变量和环境变量
用unset
消除本地变量和环境变量的示例如下:
2.2.4. 环境变量的知识小结:
- 变量名通常要大写
- 变量可以在自身的Shell及子Shell中使用
- 常用
export
来定义环境变量 - 执行
env
默认可以显示所有的环境变量名称及对应的值 - 输出时用
$变量名
,取消时用unset 变量名
- 书写crond定时任务时要注意,脚本要用到的环境变量最好先在所执行的Shell脚本中重新定义
- 如果希望环境变量永久生效,则可以将其放在用户环境变量文件或全局环境变量文件里
2.3. 环境变量初始化与对应文件的生效顺序
在登陆Linux系统并启动一个bash shell时,默认情况下bash会在若干个文件中查找环境变量的设置。这些文件可统称为系统环境文件。bash检查的环境变量文件的情况取决于系统运行Shell的方式。系统运行Shell的方式一般有3种:
- 通过系统用户登陆后默认运行的Shell
- 非登陆交互式运行Shell
- 执行脚本运行非交互式Shell
当用户登陆Linux系统时,Shell会作为登陆Shell启动。此时的登陆Shell加载环境变量的顺序如下图所示:
- 用户登陆系统后首先会加载
/etc/profile
全局环境变量文件,这是Linux系统上默认的Shell主环境变量文件。系统上每个用户登陆都会加载这个文件。 - 当加载完
/etc/profile
文件后,才会执行/etc/profile.d/
目录下的脚本文件,这个目录下的脚本文件有很多,例如:系统的字符集设置(/etc/sysyconfig/i18n
)等,再比如开发跳板机时,我们也把脚本的起始加载放到这个目录下,以便用户登陆后即可运行脚本。 - 之后开始运行
~/.bash_profile
(用户环境变量文件),在这个文件中,又会去找~/.bashrc
(用户环境变量文件),如果有,则执行,如果没有,则不执行。在~/.bashrc
文件中又会去找/etc/bashrc
(全局环境变量文件),如果有,则执行,如果没有,则不执行。 - 如果用户的Shell不是登陆时启动的(比如手动敲下bash时启动或者其它不需要输入密码的登陆及远程SSH连接情况),那么这种非登陆Sheel只会加载
~/.bashrc
(用户环境变量文件),并会去找/etc/bashrc
(全局环境变量文件)。因此如果希望在非登陆Shell下也可读到设置的环境变量等内容,就需要将变量设定等写入~/.bashrc
或者/etc/bashrc
,而不是~/.bash_profile
或/etc/profile
。
3. 普通变量
3.1. 定义本地变量
本地变量在用户当前Shell生存期的脚本中使用。
3.1.1. 普通变量定义
为普通变量的定义赋值,一般有以下3种写法:
变量名=值
赋值时不加引号变量名='值'
赋值时不加单引号变量名="值"
赋值时加双引号
3.1.2. 在Shell中定义变量名及为变量内容赋值的要求
变量名一般是由字母、数字、下划线组成的,可以以字母或下划线开头,例如:theshu、theshu123、theshu_wang。
变量的内容可以用单引号或双引号引起来,也可不加引号,但是这三者的含义是不同的,具体参见后文。
3.1.3. 普通变量的定义及输出的示例
例子1:采用不同的方式对普通变量进行定义,并一一打印输出:
- 提示:
$变量名
表示输出变量,可以用$c
和${c}
两种方法。
例子1的输出结果如下:
可见,将连续的普通字符串的内容赋值给变量,不管用不用引号,或者不管用什么引号,它的内容是什么,打印变量时就会输出什么。
例子2:接着例子1的结果,再在Linux命令行下继续输入如下内容,a、b、c的输出结果又各是什么呢?
答案如下:
3.1.4. 变量定义的基本技巧总结
在这里以上面的例子2来说明:
第一种:a=192.168.1.2-$a
。这种定义变量的方式是不加任何引号直接定义变量的内容,当内容为简单连续的数字、字符串、路径名时,可以这样用,例如:a=1,b=theshu等。不加引号时,值里有变量的会被解析后再输出,上述变量定义中因为$a
的被解析为192.168.1.2
,因此新的a值就是192.168.1.2-192.168.1.2
第二种:b='192.168.1.2-$a'
。这种定义变量的方式是通过单引号定义。这种定义方式的特点是:输出变量内容时单引号里是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合于定义显示纯字符串的情况,即不希望解析变量、命令等的场景,因此,对于这里的b的值,定义时看到的是什么就输出什么,即:192.168.1.2-$a
。
第三种:c="192.168.1.2-$a"
。这种定义变量的方式是通过双引号定义变量。这种定义方式的特点是:输出变量内容时引号里的变量及命令会经过解析后再输出内容,而不是把双引号中的变量名及命令(命令需要反引起来)原样输出。这种方式比较适合于字符串中附带有变量及命令且想将其解析后再输出的变量定义。
经验:数字内容的变量定义可以不加引号,其他没有特别要求的字符串等定义最好都加上双引号,如果真的需要原样输出就加单引号,定义变量加双引号是最常见的使用方式。
3.1.5. 把一个命令的结果作为变量的内容赋值的方法
对需要获取命令结果的变量内容赋值的常见方法有两种:
例子1:用两种方法把命令的结果赋值给变量:
提示:生产场景中把命令的结果作为变量的内容进行复制的方法在脚本开发时很常见。
3.1.6. 局部(普通)变量定义及赋值的经验小结
常规普通变量定义:
- 若变量内容为连续的数字或字符串,赋值时,变量内容两边可以不加引号,例如
a=123
- 变量的内容很多时,如果有空格且希望解析内容中的变量,就加双引号,例如
a="/etc/rc.local $USER"
,此时输出变量会对内容中的$USER
进行解析然后再输出。 - 希望原样输出变量中的内容时就用单引号引起内容进行赋值,例如
a='$USER'
。
希望变量的内容是命令的解析结果的定义及赋值如下:
- 要使用反引号将赋值的命令括起来;或者用$()括起来,例如:
a=$(ls)
变量的输出方法如下:
- 使用
$变量名
即可输出变量的内容,常用echo $变量名
的方式,也可用printf代替echo输出更复杂的格式内容。
变量定义的技巧及注意事项:
- 注意命令变量内容前后的字符(即反引号)
- 在变量名前加
$
可以取得该变量的值,使用echo
或printf
命令可以显示变量的值,$A
和${A}
的写法不同,但效果是一样的。 - 用
echo
等命令输出变量的时候,也可以用单引号、双引号、反引号,例如:echo $A
、echo "$A"
、echo '$A'
,它们的用法和前面变量内容定义的总结是一致的。 $dbname_tname
,当变量后面连接有其它字符的时候,必须给变量加上大括号{}
,例如:$dbname_tname
就要改成${dbname}_tname
。
3.2. 变量定义及变量输出说明
有关Shell变量定义、赋值及变量输出加单引号、双引号、反引号与不加引号的简要说明如下表:
命令 | 解释 |
---|---|
单引号 | 所见即所得,即输出时会将单引号内的所有内容都原样输出,或者描述为单引号里面看到的是什么就会输出什么,这称为强引用 |
双引号(默认) | 输出双引号内的所有内容;如果内容中有命令(要反引下)、变量、特殊转义符等,会先把变量、命令、转义字符解析出结果,然后再输出最终内容,推荐使用,这称为弱引用 |
无引号 | 赋值时,如果变量内容中有空格,则会造成赋值不完整。而在输出内容时,会将含有空格的字符串视为一个整体来输出;如果内容中有命令(要反引下)、变量等,则会先把变量、命令解析出结果,然后输出最终内容;如果字符串中带有空格等特殊字符,则有可能无法完整地输出,因此需要改加双引号。一般连续的字符串、数字、路径等可以不加任何引号进行赋值和输出,不过最好是用双引号替代无引号的情况,特别是对变量赋值时 |
反引号 | 反引号一般用于引用命令,执行的时候命令会被执行,相当于$() ,赋值和输出都要用反引号将命令引起来 |
提示:这里仅为Linux Shell下的结论,对于awk语言会有点特别。下文示例中会有说明。
高手前辈的建议是:
- 在脚本中定义普通字符串变量时,应尽量把变量的内容用双引号括起来
- 单纯数字的变量内容可以不加引号
- 希望变量内容原样输出时需要加单引号
- 希望变量值引用命令并获取命令的结果时就用反引号或
$()
3.3. 单引号、双引号与不加引号的实战演示
例子1:对
由反引号引起来的date
命令或$(date)
进行测试。1234567891011121314[theshu@theshu ~]$ echo 'today is date'today is date[theshu@theshu ~]$ echo 'tody is `date`'tody is `date`[theshu@theshu ~]$ echo "today is date"today is date[theshu@theshu ~]$ echo "today is `date`"today is Sun Feb 25 12:13:42 CST 2018[theshu@theshu ~]$ echo "today is $(date)" #<==对于连续的字符串等内容输出一般可以不加引号,但加双引号比较保险,所以推荐使用today is Sun Feb 25 12:13:59 CST 2018[theshu@theshu ~]$ echo today is $(date) #<==带空格的内容不加引号,同样可以正确地输出,但不建议这么做today is Sun Feb 25 12:14:09 CST 2018例子2:变量定义后,在调用变量输出打印时加引号测试。
1234567[theshu@theshu ~]$ OLDBOY=testchars[theshu@theshu ~]$ echo $OLDBOYtestchars[theshu@theshu ~]$ echo '$OLDBOY'$OLDBOY[theshu@theshu ~]$ echo "$OLDBOY"testchars例子3:使用三剑客中的
grep
过滤字符串时给过滤的内容加引号。123456789[theshu@theshu ~]$ cat grep.log #<==待测试的内容,THESHU变量值为theshutestcharstheshu[theshu@theshu ~]$ grep "$THESHU" grep.logtheshu[theshu@theshu ~]$ grep '$THESHU' grep.log[theshu@theshu ~]$ grep $THESHU grep.log #<==同双引号的情况,但不建议这样使用,没有特殊需求时应一律加双引号theshu例子4:使用
awk
调用Shell中的变量,分别针对加引号、不加引号等情况进行测试。
首先在给Shell中的变量赋值时不加任何引号,这里使用awk
输出测试结果:
以上结果正好与前面的结论相反,这是awk
调用Shell变量的特殊用法。
然后在给Shell中的变量赋值时加单引号,同样使用awk
输出测试结果:
接着在给Shell中的变量赋值时加双引号,也使用awk
输出测试结果:
最后在给Shell中的变量赋值时加反引号引用命令,同样使用awk
输出测试结果:
结论:不管变量如何定义、赋值,除了加单引号以外,利用awk
直接获取变量的输出,结果都是一样的,因此,在awk
取用Shell变量时,我们更多的还是喜欢先用echo $变量
输出变量,然后通过管道给awk
,进而控制变量的输出结果。举例如下:
这就符合前面给出的普通情况的结论了。下面再来用sed
测试一下指定变量关键字的过滤:
所以得到结论,sed
和grep
的测试和前面的结论是相符的,唯有awk
有些特殊。
3.4. 关于定义普通字符串变量的建议
- 内容是纯数字、简单的连续字符(内容中不带有任何空格)时,定义时可以不加任何引号,例如:
theshuAge=24
- 没有特殊情况时,字符串一律用双引号定义赋值,特别是多个字符串中间有空格时,例如:
myName="Theshu is a handsome boy."
- 当变量里的内容需要原样输出时,要用单引号,这样的需求极少,例如:
THESHU_NAME='theshu'
4. 变量定义技巧总结
可以多学习和模仿操作系统自带的/etc/init.d/functions
函数库脚本的定义思路,多学习Linux系统脚本中的定义,有经验的话最终应形成一套适合自己的规范和习惯。
- 变量名及变量内容定义小结
- 变量名只能为字母、数字或下划线,只能以字母或下划线开头。
- 变量名的定义要有一定的规范,并且要见名知意。建议用驼峰命名法。
- 一般的变量定义、赋值常用双引号;简单连续的字符串可以不加引号;希望原样输出时使用单引号。
- 希望变量的内容是命令的解析结果时,要用
反引号
,或者用$()
把命令括起来再赋值。
- Shell定义变量时使用
=
的知识a=1
里等号是赋值的意思;比较变量是否相等时也可以用=
或==
。
- 打印输出及使用变量的知识
- 打印输出或使用变量时,变量名前要接
$
符号;变量名后面紧接其它字符的时候,要用大括号将变量部分单独括起来,以防止出现金庸新著
的问题;在unset
、export
、(())
等场景中使用但不打印变量时不加$
,这个有些例外。 - 打印输出或使用变量时,一般加双引号或不加引号;如果是字符串变量,最好加双引号;希望原样输出时使用单引号。
- 打印输出或使用变量时,变量名前要接
OK